/*----------------------------------------------------------\\
|| Mancala                                                  ||
|| CS-340 with Professor Troy                               ||
|| Joshua Jung and Eugene Magdel                            ||
|| April 29, 2011                                           ||
\\----------------------------------------------------------*/
#include "mancala.h"
#include "ui_mancala.h"

#include <QApplication>
#include <QtNetwork/QTcpSocket>
#include <QtNetwork/QTcpServer>
#include <QMessageBox>
#include <QDataStream>
#include <QtNetwork>
#include <QByteArray>
#include <QGraphicsPixmapItem>
#include <QPalette>
#include <QColor>
#include <QTimer>

#include "sstream"

bool exiting = false;

//-------------------------------------
// Constructor
//-------------------------------------
Mancala::Mancala(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::Mancala), hosting(false), connected(false), socket(NULL), server(NULL),
        isOurTurn(false), board(NULL), gameType(GT_UNDEFINED), playing(false), haveWinner(false)
{
    ui->setupUi(this);

    ui->statusBar->hide();

    ui->lP1Turn->setVisible(false);
    ui->lP0Turn->setVisible(false);
    ui->lP0Won->setVisible(false);
    ui->lP1Won->setVisible(false);
    ui->lHighscore->setVisible(false);
    ui->leHighScoreName->setVisible(false);
    ui->btnHighScoreSave->setVisible(false);

    buttonsByIndex = new QPushButton *[14];

    timer = new QTimer();

    connect(timer, SIGNAL(timeout()), this, SLOT(renderCups()));
    timer->start(20);

    initialize();
}

//-------------------------------------
// Destructor
//-------------------------------------
Mancala::~Mancala()
{
    delete ui;

    if (server != NULL)
    {
        delete server;
    }

    if (socket != NULL)
    {
        delete socket;
    }
}

//-------------------------------------
// initialize
//-------------------------------------
/**
 * Initialize all our non-qt-generated stuff... like the graphics scene.
 */
void Mancala::initialize()
{
    QPushButton *pb = NULL;

    for (int i = 0; i < 14; i++)
    {
        switch (i) {
        case 0: pb = ui->b0; break;
        case 1: pb = ui->b1; break;
        case 2: pb = ui->b2; break;
        case 3: pb = ui->b3; break;
        case 4: pb = ui->b4; break;
        case 5: pb = ui->b5; break;
        case 6: pb = ui->b6; break;
        case 7: pb = ui->b7; break;
        case 8: pb = ui->b8; break;
        case 9: pb = ui->b9; break;
        case 10: pb = ui->b10; break;
        case 11: pb = ui->b11; break;
        case 12: pb = ui->b12; break;
        case 13: pb = ui->b13; break;
        }

        buttonsByIndex[i] = pb;
    }

    graphicsScene = new QGraphicsScene(0, 0, 541, 147);

    ui->graphics->setScene(graphicsScene);

    QPixmap *pm = new QPixmap("../cs340-mancala/images/board.gif");
    pm_gem = new QPixmap("../cs340-mancala/images/gem.png");
    //std::cout << QDir::currentPath().toStdString() << endl;
    QGraphicsPixmapItem *pmi = graphicsScene->addPixmap(*pm);

    pmi->setX(0);
    pmi->setY(0);

    pmi->setScale(1);

    //resetAll();
}

//-------------------------------------
// resetAll
//-------------------------------------
/**
 * This resets the entire game.
 */
void Mancala::resetAll()
{
    ui->lP1Turn->setVisible(false);
    ui->lP0Turn->setVisible(false);
    ui->lP0Won->setVisible(false);
    ui->lP1Won->setVisible(false);
    ui->lHighscore->setVisible(false);
    ui->leHighScoreName->setVisible(false);
    ui->btnHighScoreSave->setVisible(false);

    playing = false;
    haveWinner = false;

    ui->widgetStack->setCurrentIndex(0);

    disconnectAll();

    setIsOurTurn(false);
}

//-------------------------------------
// disconnectAll
//-------------------------------------
/**
 * This disconnects all active connections.
 */
void Mancala::disconnectAll()
{
    connected = false;

    hosting = false;

    if (server != NULL)
    {
        server->close();

        server = NULL;

        delete server;
    }

    if (socket != NULL)
    {
        socket->abort();

        socket = NULL;

        delete socket;
    }

}

//-------------------------------------
// writeByteImmediate
//-------------------------------------
void Mancala::writeByteImmediate(qint8 b)
{
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_0);

    qDebug() << "Writing: " << b;

    out << b;

    socket->write(block);
}

//-------------------------------------
// startHostDrivenNetworkGame
//-------------------------------------
/**
 * Starts a new network game in the host... determines who goes first etc.
 */
void Mancala::startHostDrivenNetworkGame()
{
    srand(time(NULL));

    setupGameBoard();

    float r = (rand() % 100) / 100.0;

    setIsOurTurn(r >= 0.51);

    writeByteImmediate(r >= 0.51 ? 19 : 20);
}

//-------------------------------------
// setupGameBoard
//-------------------------------------
/**
 * This sets up the game board for a new game.
 */
void Mancala::setupGameBoard()
{
    ui->lP0Won->setVisible(false);
    ui->lP1Won->setVisible(false);
    ui->lP1Turn->setVisible(false);
    ui->lP0Turn->setVisible(false);

    // First we need to wipe out any current game data (e.g. gems being displayed)
    if (board != NULL)
    {
        Cup *c;

        for (int i = 0; i < 14; i++)
        {
            c = &Cup::cup_at(i);

            for (int j = 0; j < c->gem_count(); j++)
            {
                Gem *g = c->getGemAt(j);

                graphicsScene->removeItem(g->pmi);
            }
        }
    }

    ui->widgetStack->setCurrentIndex(3);

    if (board != NULL)
    {
        delete board;
    }
    board = new Board();
    if (!hosting && gameType == GT_NETWORK)
    {
        board->set_turn(1);
    }
    else if (hosting && gameType == GT_NETWORK)
    {
        board->set_turn(0);
    }

    playing = true;

    renderCups();
}

//-------------------------------------
// setupGameBoard
//-------------------------------------
/**
 * Renders the gems on the board
 */
void Mancala::renderCups()
{
    if (playing)
    {
        if (!haveWinner && board->get_turn() == 1 && gameType == GT_AI)
        {
            aiDelay ++;

            // 3 second delay on when ai goes next.
            if (aiDelay >= 15)
            {
                aiDelay = 0;

                int ai_move = ai_computer -> next_move();

                while(board -> move(ai_move)){
                    if (board->winner_is() != -1)
                    {
                        break;
                    }

                    ai_move = ai_computer -> next_move();
                }

                checkWin();

                board->toggle_turn();

                setIsOurTurn(true);
            }
        }

        Cup *c;

        for (int i = 0; i < 14; i++)
        {
            c = &Cup::cup_at(i);

            buttonsByIndex[i]->setText(QString::number(c->gem_count()));

            if (i == CUP_SCORE_P1)
            {
                renderCupGemsInBounds(c, 40, 20, 22, 80);
            }
            else if (i == CUP_SCORE_P0)
            {
                renderCupGemsInBounds(c, 490, 20, 22, 80);
            }
            else if (i >= CUP_START_P0 && i < CUP_START_P0 + 7)
            {
                renderCupGemsInBounds(c, 94 + ((i - 1) * 60) + (i >= CUP_START_P0 + 3 ? 40 : 0), 85, 22, 30);
            }
            else if (i >= CUP_START_P1 && i < CUP_START_P1 + 7)
            {
                renderCupGemsInBounds(c, 429 - ((i - 8) * 60) - (i >= CUP_START_P1 + 3 ? 40 : 0), 20, 22, 30);
            }
        }
    }
}

/**
 * Renders the gems inside the provided cup within the
 * provided bounds.
 */
void Mancala::renderCupGemsInBounds(Cup *c, int x, int y, int w, int h)
{
    // Shift
    x -= 50;
    y -= 40;

    for (int g = 0; g < c->gem_count(); g++)
    {
        Gem *gem = c->getGemAt(g);

        if (gem->pmi == NULL)
        {
            gem->pmi = graphicsScene->addPixmap(*pm_gem);
        }

        if (gem->getX() == -1 || gem->getX() < x || gem->getY() < y || gem->getX() > x + w || gem->getY() > y + h)
        {
            // This gem needs to get a new position.
            gem->setPos(x + (rand() % w), y + (rand() % h));
        }

        gem->updatePos(25);

        gem->pmi->setScale(0.6);
    }
}

//-------------------------------------
// setIsOurTurn
//-------------------------------------
/**
 * Turns on or off and enables / disables appropriate items based
 * on whether it is our turn or not.
 *
 * When in 2 player local mode this is called just to update the appropriate
 * buttons
 */
void Mancala::setIsOurTurn(bool is)
{
    isOurTurn = is;
    ui->lMessage->setVisible(!is);
    ui->lMessage->setText("Wait for turn...");
    ui->lP1Turn->setVisible(board != NULL ? board->get_turn() == 1 && is : false);
    ui->lP0Turn->setVisible(board != NULL ? board->get_turn() == 0 && is : false);
}

//-------------------------------------
// checkWin
//-------------------------------------
void Mancala::checkWin()
{
    int winner = board->winner_is();

    if (winner == 0)
    {
        ui->lP0Won->setVisible(true);
        ui->lP1Turn->setVisible(false);
        ui->lP0Turn->setVisible(false);

        haveWinner = true;
    }
    else if (winner == 1)
    {
        ui->lP1Won->setVisible(true);
        ui->lP1Turn->setVisible(false);
        ui->lP0Turn->setVisible(false);

        haveWinner = true;
    }

    // Do we need to get the highscore?
    if (haveWinner)
    {
        if ((connected && hosting && Highscore::is_top_5_score(board->get_scores(1))) ||
            (connected && !hosting && Highscore::is_top_5_score(board->get_scores(0))))
        {
            // Request high score from other side.
            writeByteImmediate(31);
        }
        else if (Highscore::is_top_5_score(board->get_scores(1)) || Highscore::is_top_5_score(board->get_scores(0)))
        {
            slot_getHighScore();
        }
    }
}

//-------------------------------------
// slot_help
//-------------------------------------
void Mancala::slot_help()
{
    QMessageBox::information(this, tr("Help"), tr("Mancala is a game where you capture jewels and the player with the highest number of jewels at the end wins. \nSee instructions on the wikipedia article.\n\nTo connect to a network game, the host should choose new network game from the 'Game' menu. He should then press the 'Join / Host' button without entering an IP address. \nThe client then presses ") +
                             tr("the net network game button in the 'Game' menu and enters the IP address listed on the host players screen. This will start a new network game."));
}

//-------------------------------------
// slot_about()
//-------------------------------------
void Mancala::slot_about()
{
    QMessageBox::information(this, tr("About"), tr("Mancala\nDeveloped in C++ by Joshua Jung and Eugene Magdel for CS-340 with Professor Troy at UIC in April 2011.\n\nThis software comes without warranty."));
}

//-------------------------------------
// slot_getHighScore
//-------------------------------------
void Mancala::slot_getHighScore()
{
    ui->lHighscore->setVisible(true);
    ui->leHighScoreName->setVisible(true);
    ui->btnHighScoreSave->setVisible(true);
}

//-------------------------------------
// slot_highScoreEntered
//-------------------------------------
void Mancala::slot_highscoreSave()
{
    int highscore = board->get_scores(0) > board->get_scores(1) ? board->get_scores(0) : board->get_scores(1);

    if (ui->leHighScoreName->text().length() > 0 || highscoreRequested)
    {
        ui->lHighscore->setVisible(false);
        ui->leHighScoreName->setVisible(false);
        ui->btnHighScoreSave->setVisible(false);

        if (connected && highscoreRequested)
        {
            QByteArray block;
            QDataStream out(&block, QIODevice::WriteOnly);
            out.setVersion(QDataStream::Qt_4_0);

            qDebug() << "Writing: " << 30 << ui->leHighScoreName->text();

            writeByteImmediate(30);

            out << ui->leHighScoreName->text();

            socket->write(block);
        }

        if (Highscore::is_top_5_score(highscore))
        {
            QMessageBox::information(this, tr("Highscore Saved!"), tr("Your highscore has been saved for future posterity to love your amazingness."));

            Highscore::add(ui->leHighScoreName->text().toStdString(), highscore);
        }
    }
}

//-------------------------------------
// slot_newLocalGame2Player
//-------------------------------------
void Mancala::slot_newLocalGame2Player()
{
    resetAll();

    playing = true;

    gameType = GT_LOCAL;
    setupGameBoard();

    setIsOurTurn(true);
}

//-------------------------------------
// slot_startNetworkGame
//-------------------------------------
void Mancala::slot_startNetworkGame()
{
    resetAll();

    gameType = GT_NETWORK;

    ui->widgetStack->setCurrentIndex(1);
}

//-------------------------------------
// slot_hostJoinGame
//-------------------------------------
void Mancala::slot_hostJoinGame()
{
    disconnectAll();

    hosting = ui->leIP->text().isEmpty();

    if (hosting)
    {
        if (server == NULL)
        {
            server = new QTcpServer(this);
        }

        if (!server->listen(QHostAddress::Any, 34567))
        {
            QMessageBox::warning(this, tr("Server Error"), tr("Unable to start server on this machine."));
        }

        connect(server, SIGNAL(newConnection()), this, SLOT(slot_newConnection()));

        bool found = false;

        foreach(QNetworkInterface interface, QNetworkInterface::allInterfaces())
        {
            if (interface.flags().testFlag(QNetworkInterface::IsRunning))
            {
                foreach (QNetworkAddressEntry entry, interface.addressEntries())
                {
                    if ( interface.hardwareAddress() != "00:00:00:00:00:00" && entry.ip().toString().contains("."))
                    {
                        ui->lHost->setText(tr("Waiting for client at IP: ") + entry.ip().toString() + tr("..."));

                        found = true;

                        break;
                    }
                }
            }

            if (found)
                break;
        }

        ui->widgetStack->setCurrentIndex(2);
    }
    else
    {
        if (socket == NULL)
        {
            socket = new QTcpSocket(this);
        }

        connect(socket, SIGNAL(disconnected()), this, SLOT(slot_clientDisconnected()));
        connect(socket, SIGNAL(readyRead()), this, SLOT(slot_readReady()));

        socket->connectToHost(ui->leIP->text(), 34567);
    }
}

//-------------------------------------
// slot_newConnection
//-------------------------------------
void Mancala::slot_newConnection()
{
    socket = server->nextPendingConnection();

    connect(socket, SIGNAL(disconnected()), this, SLOT(slot_clientDisconnected()));
    connect(socket, SIGNAL(readyRead()), this, SLOT(slot_readReady()));

    // This will be the first 16 bits sent to the client to confirm a valid connection.
    // When this is received by the client, the client will ping back 17 and then the
    // server and client will both start a new game.
    writeByteImmediate(18);
}

//-------------------------------------
// slot_clientDisconnected
//-------------------------------------
void Mancala::slot_clientDisconnected()
{
    // We don't want to display this message at all if WE are the ones quitting... in
    // which case both of these will be NULL
    if (!exiting)
    {
        QMessageBox::information(this, tr("Player Disconnected"), tr("Ending game due to network disconnect."));

        resetAll();
    }
}

//-------------------------------------
// slot_readReady
//-------------------------------------
void Mancala::slot_readReady()
{
    if (socket->bytesAvailable() < (int)sizeof(qint8))
    {
        return;
    }

    QDataStream in(socket);
    in.setVersion(QDataStream::Qt_4_0);

    qint8 oxed;

    in >> oxed;

    if (!connected)
    {
        if (hosting)
        {

            // At this point we are just validating the connection. The client should have responded
            // with 17
            if (oxed == 17)
            {
                connected = true;

                startHostDrivenNetworkGame();
            }
            else
            {
                connected = false;

                QMessageBox::information(this, tr("Connect Error"), tr("The client responded with an invalid message. Cancelling Game."));

                resetAll();
            }
        } // hosting
        else
        {

            // At this point we are just validating the connection. The server should have sent
            // 18 as the first message.
            if (oxed == 18)
            {
                connected = true;

                writeByteImmediate(17);

                setupGameBoard();
            }
            else
            {
                connected = false;

                QMessageBox::information(this, tr("Connect Error"), tr("The server responded with an invalid message. Cancelling Game."));

                resetAll();
            }
        }// !hosting
    } //!connected
    else
    {
        if (oxed == 31)
        {
            highscoreRequested = true;

            // High score name is REQUESTED by other side.
            slot_getHighScore();
        }
        else if (oxed == 30)
        {
            // High score name is coming through
            QString name;
            in >> name;

            Highscore::add(name.toStdString(), board->get_scores(hosting ? 1 : 0));

            slot_highscores();
        }
        else if (oxed == 19 || oxed == 20)
        {
            switch (oxed)
            {
                // HOST STARTS GAME (we are client if this is received)
            case 19 :
                setIsOurTurn(false);
                break;
                // CLIENT STARTS GAME (we are client if this is received)
            case 20 :
                setIsOurTurn(true);
                break;
            }
        }
        else
        {
            // The other player has clicked to move. We need to mirror that on our end.
            int pressed_index = oxed;

            if (pressed_index < 1 || pressed_index > 13)
            {
                QMessageBox::information(this, tr("Connect Error"), tr("The server responded with a bad move: ") + QString::number((int)pressed_index));
            }
            else
            {
                qDebug() << "Other player pressed: " << pressed_index << " or " << oxed;

                if (!board -> move(pressed_index, hosting ? 1 : 0))
                {
                    setIsOurTurn(true);
                }

                checkWin();

                renderCups();
            }
        }
    }
}

//-------------------------------------
// slot_move
//-------------------------------------
void Mancala::slot_move()
{
    if (haveWinner)
        return;

    if (gameType == GT_LOCAL)
    {
        QPushButton * button = qobject_cast<QPushButton *>(QObject::sender ());
        int pressed_index = button -> objectName().replace("b", "").toInt();

        if (!board -> move(pressed_index))
        {
            board->toggle_turn();

            setIsOurTurn(true);
        }

        checkWin();
    }
    else if (gameType == GT_NETWORK)
    {
        if (isOurTurn)
        {
            QPushButton * button = qobject_cast<QPushButton *>(QObject::sender ());
            int pressed_index = button -> objectName().replace("b", "").toInt();

            if (Cup::cup_at(pressed_index).get_owner() == board->get_turn())
            {
                if (!board -> move(pressed_index))
                {
                    writeByteImmediate((char)pressed_index);

                    setIsOurTurn(!isOurTurn);
                }
                else
                {
                    writeByteImmediate((char)pressed_index);
                }

                checkWin();
            }
        }
    }
    else if(gameType == GT_AI){
        if (isOurTurn && board->get_turn() != 1)
        {
            QPushButton * button = qobject_cast<QPushButton *>(QObject::sender ());
            int pressed_index = button -> objectName().replace("b", "").toInt();
            if (!board -> move(pressed_index)){

                checkWin();

                board->toggle_turn();

                setIsOurTurn(true);
            }
        }
    }
}


//-------------------------------------
// slot_quit
//-------------------------------------
void Mancala::slot_quit()
{
    exiting = true;

    disconnectAll();

    QApplication::exit(0);
}

//-------------------------------------
// slot_highscores
//-------------------------------------
void Mancala::slot_highscores(){
    Score** scores = new Score*[5];
    Highscore::top(5, scores);
    QString top_scores = "";
    for(int i=0; i<5; ++i){
        if(scores[i]==NULL)
            break;
        QString row = QString::fromStdString(scores[i] -> name) + ": " + QString::number(scores[i] -> score) + QString::fromStdString("\n");
        top_scores.append(row);
    }
    QMessageBox::information(this, tr("Top 5 Scores"), top_scores);
}

//-------------------------------------
// slot_local_ai
//-------------------------------------
void Mancala::slot_local_ai(){
    resetAll();

    aiDelay = 0;

    cout << "!!!!!!!!!!!!" << endl;

    gameType = GT_AI;
    setupGameBoard();
    board->set_turn(0);
    setIsOurTurn(true);
    ai_computer = new MancalaAi(1);

    playing = true;
}
